"""
约束满足算法实现模块

用于处理排班问题中的各种硬约束和软约束。
"""

import random
import logging
from datetime import datetime, timedelta


class ConstraintSolver:
    """约束满足算法实现"""

    def __init__(self):
        """初始化约束满足求解器"""
        self.logger = logging.getLogger(__name__)

        # 硬约束列表
        self.hard_constraints = [
            self.check_shift_coverage,  # 班次覆盖约束
            self.check_max_consecutive_shifts,  # 最大连续班次约束
            self.check_min_rest_time,  # 最小休息时间约束
            self.check_max_shifts_per_week,  # 每周最大班次数约束
        ]

        # 软约束列表
        self.soft_constraints = [
            self.check_caregiver_preferences,  # 护理员偏好约束
            self.check_skill_match,  # 技能匹配约束
            self.check_workload_balance,  # 工作负载平衡约束
            self.check_shift_continuity,  # 班次连续性约束
        ]

    def solve(
        self, initial_schedule, caregivers, elders, shift_config, max_iterations=1000
    ):
        """使用约束满足算法求解排班问题

        Args:
            initial_schedule: 初始排班方案
            caregivers: 护理员列表
            elders: 老人列表
            shift_config: 班次配置
            max_iterations: 最大迭代次数

        Returns:
            满足约束的排班方案
        """
        self.logger.info("开始使用约束满足算法求解排班问题...")
        start_time = datetime.now()

        current_schedule = initial_schedule
        best_schedule = current_schedule
        best_violations = self.count_constraint_violations(
            current_schedule, caregivers, elders, shift_config
        )

        self.logger.info(
            f"初始方案约束违反数: 硬约束={best_violations['hard']}, 软约束={best_violations['soft']}"
        )

        # 迭代优化
        for iteration in range(max_iterations):
            # 生成邻域解
            neighbor = self.generate_neighbor(
                current_schedule, caregivers, elders, shift_config
            )

            # 计算约束违反数
            neighbor_violations = self.count_constraint_violations(
                neighbor, caregivers, elders, shift_config
            )
            current_violations = self.count_constraint_violations(
                current_schedule, caregivers, elders, shift_config
            )

            # 如果邻域解更好，则接受
            if self.is_better_solution(neighbor_violations, current_violations):
                current_schedule = neighbor

                # 更新最佳解
                if self.is_better_solution(neighbor_violations, best_violations):
                    best_schedule = neighbor
                    best_violations = neighbor_violations
                    self.logger.info(
                        f"第 {iteration} 次迭代发现更好的解，约束违反数: 硬约束={best_violations['hard']}, 软约束={best_violations['soft']}"
                    )

            # 每100次迭代输出一次日志
            if iteration % 100 == 0:
                self.logger.info(
                    f"第 {iteration} 次迭代，当前最佳约束违反数: 硬约束={best_violations['hard']}, 软约束={best_violations['soft']}"
                )

            # 如果已经找到满足所有硬约束的解，且软约束违反数很少，则提前结束
            if best_violations["hard"] == 0 and best_violations["soft"] <= 5:
                self.logger.info(
                    "已找到满足所有硬约束且软约束违反数很少的解，提前结束迭代"
                )
                break

        end_time = datetime.now()
        optimization_time = (end_time - start_time).total_seconds()
        self.logger.info(
            f"约束满足算法求解完成，耗时 {optimization_time:.2f} 秒，最终约束违反数: 硬约束={best_violations['hard']}, 软约束={best_violations['soft']}"
        )

        return best_schedule

    def count_constraint_violations(self, schedule, caregivers, elders, shift_config):
        """计算排班方案的约束违反数

        Args:
            schedule: 排班方案
            caregivers: 护理员列表
            elders: 老人列表
            shift_config: 班次配置

        Returns:
            约束违反数字典，包含硬约束和软约束的违反数
        """
        hard_violations = 0
        soft_violations = 0

        # 检查硬约束
        for constraint in self.hard_constraints:
            violations = constraint(schedule, caregivers, elders, shift_config)
            hard_violations += violations

        # 检查软约束
        for constraint in self.soft_constraints:
            violations = constraint(schedule, caregivers, elders, shift_config)
            soft_violations += violations

        return {"hard": hard_violations, "soft": soft_violations}

    def is_better_solution(self, violations1, violations2):
        """判断violations1是否比violations2更好

        Args:
            violations1: 第一个约束违反数字典
            violations2: 第二个约束违反数字典

        Returns:
            如果violations1更好，则返回True
        """
        # 首先比较硬约束违反数
        if violations1["hard"] < violations2["hard"]:
            return True
        elif violations1["hard"] > violations2["hard"]:
            return False

        # 硬约束违反数相同，比较软约束违反数
        return violations1["soft"] < violations2["soft"]

    def generate_neighbor(self, schedule, caregivers, elders, shift_config):
        """生成邻域解

        Args:
            schedule: 当前排班方案
            caregivers: 护理员列表
            elders: 老人列表
            shift_config: 班次配置

        Returns:
            邻域解
        """
        # 深拷贝当前排班方案
        neighbor = self.deep_copy_schedule(schedule)

        # 随机选择一种邻域操作
        operation = random.choice(["swap", "move", "reassign"])

        if operation == "swap":
            # 交换两个护理员的班次
            self.swap_caregivers(neighbor)
        elif operation == "move":
            # 移动一个班次
            self.move_shift(neighbor, shift_config)
        else:
            # 重新分配一个护理员
            self.reassign_caregiver(neighbor, caregivers)

        return neighbor

    def deep_copy_schedule(self, schedule):
        """深拷贝排班方案

        Args:
            schedule: 排班方案

        Returns:
            排班方案的深拷贝
        """
        # 这里需要根据实际的排班方案数据结构进行实现
        # 简单起见，假设排班方案是一个字典
        return {
            key: value.copy() if isinstance(value, list) else value
            for key, value in schedule.items()
        }

    def swap_caregivers(self, schedule):
        """交换两个护理员的班次

        Args:
            schedule: 排班方案
        """
        if (
            "shift_assignments" not in schedule
            or len(schedule["shift_assignments"]) < 2
        ):
            return

        # 随机选择两个不同的班次分配
        idx1, idx2 = random.sample(range(len(schedule["shift_assignments"])), 2)

        # 交换护理员
        (
            schedule["shift_assignments"][idx1].caregiver_id,
            schedule["shift_assignments"][idx2].caregiver_id,
        ) = (
            schedule["shift_assignments"][idx2].caregiver_id,
            schedule["shift_assignments"][idx1].caregiver_id,
        )

    def move_shift(self, schedule, shift_config):
        """移动一个班次

        Args:
            schedule: 排班方案
            shift_config: 班次配置
        """
        if "shift_assignments" not in schedule or not schedule["shift_assignments"]:
            return

        # 随机选择一个班次分配
        idx = random.randint(0, len(schedule["shift_assignments"]) - 1)

        # 随机选择一个新的班次类型
        new_shift_type = random.choice(list(shift_config.keys()))

        # 更新班次类型
        schedule["shift_assignments"][idx].shift_type = new_shift_type

    def reassign_caregiver(self, schedule, caregivers):
        """重新分配一个护理员

        Args:
            schedule: 排班方案
            caregivers: 护理员列表
        """
        if (
            "shift_assignments" not in schedule
            or not schedule["shift_assignments"]
            or not caregivers
        ):
            return

        # 随机选择一个班次分配
        idx = random.randint(0, len(schedule["shift_assignments"]) - 1)

        # 随机选择一个新的护理员
        new_caregiver = random.choice(caregivers)

        # 更新护理员
        schedule["shift_assignments"][idx].caregiver_id = new_caregiver.id

    # 硬约束检查函数

    def check_shift_coverage(self, schedule, caregivers, elders, shift_config):
        """检查班次覆盖约束

        Args:
            schedule: 排班方案
            caregivers: 护理员列表
            elders: 老人列表
            shift_config: 班次配置

        Returns:
            违反约束的次数
        """
        if "shift_assignments" not in schedule:
            return 0

        violations = 0

        # 按日期和班次类型分组
        shifts_by_date_type = {}
        for assignment in schedule["shift_assignments"]:
            date_key = str(assignment.shift_date)
            if date_key not in shifts_by_date_type:
                shifts_by_date_type[date_key] = {}

            shift_type = assignment.shift_type
            if shift_type not in shifts_by_date_type[date_key]:
                shifts_by_date_type[date_key][shift_type] = 0

            shifts_by_date_type[date_key][shift_type] += 1

        # 检查每个日期的每个班次是否有足够的护理员
        for date_key, shifts in shifts_by_date_type.items():
            for shift_type, count in shifts.items():
                # 假设每个班次至少需要1个护理员
                min_required = 1
                if count < min_required:
                    violations += min_required - count

        return violations

    def check_max_consecutive_shifts(self, schedule, caregivers, elders, shift_config):
        """检查最大连续班次约束

        Args:
            schedule: 排班方案
            caregivers: 护理员列表
            elders: 老人列表
            shift_config: 班次配置

        Returns:
            违反约束的次数
        """
        if "shift_assignments" not in schedule:
            return 0

        violations = 0
        max_consecutive = 5  # 最大连续工作天数

        # 按护理员和日期分组
        shifts_by_caregiver = {}
        for assignment in schedule["shift_assignments"]:
            caregiver_id = assignment.caregiver_id
            if caregiver_id not in shifts_by_caregiver:
                shifts_by_caregiver[caregiver_id] = {}

            date_key = str(assignment.shift_date)
            shifts_by_caregiver[caregiver_id][date_key] = True

        # 检查每个护理员的连续工作天数
        for caregiver_id, dates in shifts_by_caregiver.items():
            # 按日期排序
            sorted_dates = sorted(dates.keys())

            # 计算最大连续工作天数
            consecutive = 1
            for i in range(1, len(sorted_dates)):
                prev_date = datetime.strptime(sorted_dates[i - 1], "%Y-%m-%d").date()
                curr_date = datetime.strptime(sorted_dates[i], "%Y-%m-%d").date()

                if (curr_date - prev_date).days == 1:
                    consecutive += 1
                else:
                    consecutive = 1

                if consecutive > max_consecutive:
                    violations += 1
                    break

        return violations

    def check_min_rest_time(self, schedule, caregivers, elders, shift_config):
        """检查最小休息时间约束

        Args:
            schedule: 排班方案
            caregivers: 护理员列表
            elders: 老人列表
            shift_config: 班次配置

        Returns:
            违反约束的次数
        """
        if "shift_assignments" not in schedule:
            return 0

        violations = 0
        min_rest_hours = 11  # 最小休息时间（小时）

        # 按护理员分组
        shifts_by_caregiver = {}
        for assignment in schedule["shift_assignments"]:
            caregiver_id = assignment.caregiver_id
            if caregiver_id not in shifts_by_caregiver:
                shifts_by_caregiver[caregiver_id] = []

            # 假设shift_date是日期，shift_type对应班次的开始和结束时间
            shift_date = assignment.shift_date
            shift_type = assignment.shift_type

            # 获取班次的开始和结束时间
            start_time = shift_config[shift_type]["start_time"]
            end_time = shift_config[shift_type]["end_time"]

            # 创建完整的日期时间对象
            start_datetime = datetime.combine(
                shift_date, datetime.strptime(start_time, "%H:%M").time()
            )

            # 处理跨天的情况
            if end_time < start_time:
                end_datetime = datetime.combine(
                    shift_date + timedelta(days=1),
                    datetime.strptime(end_time, "%H:%M").time(),
                )
            else:
                end_datetime = datetime.combine(
                    shift_date, datetime.strptime(end_time, "%H:%M").time()
                )

            shifts_by_caregiver[caregiver_id].append((start_datetime, end_datetime))

        # 检查每个护理员的休息时间
        for caregiver_id, shifts in shifts_by_caregiver.items():
            # 按开始时间排序
            sorted_shifts = sorted(shifts, key=lambda x: x[0])

            # 检查相邻班次之间的休息时间
            for i in range(1, len(sorted_shifts)):
                prev_end = sorted_shifts[i - 1][1]
                curr_start = sorted_shifts[i][0]

                rest_hours = (curr_start - prev_end).total_seconds() / 3600

                if rest_hours < min_rest_hours:
                    violations += 1

        return violations

    def check_max_shifts_per_week(self, schedule, caregivers, elders, shift_config):
        """检查每周最大班次数约束

        Args:
            schedule: 排班方案
            caregivers: 护理员列表
            elders: 老人列表
            shift_config: 班次配置

        Returns:
            违反约束的次数
        """
        if "shift_assignments" not in schedule:
            return 0

        violations = 0
        max_shifts_per_week = 5  # 每周最大班次数

        # 按护理员和周分组
        shifts_by_caregiver_week = {}
        for assignment in schedule["shift_assignments"]:
            caregiver_id = assignment.caregiver_id
            if caregiver_id not in shifts_by_caregiver_week:
                shifts_by_caregiver_week[caregiver_id] = {}

            # 计算周数
            date = assignment.shift_date
            year, week_num, _ = date.isocalendar()
            week_key = f"{year}-{week_num}"

            if week_key not in shifts_by_caregiver_week[caregiver_id]:
                shifts_by_caregiver_week[caregiver_id][week_key] = 0

            shifts_by_caregiver_week[caregiver_id][week_key] += 1

        # 检查每个护理员每周的班次数
        for caregiver_id, weeks in shifts_by_caregiver_week.items():
            for week_key, count in weeks.items():
                if count > max_shifts_per_week:
                    violations += count - max_shifts_per_week

        return violations

    # 软约束检查函数

    def check_caregiver_preferences(self, schedule, caregivers, elders, shift_config):
        """检查护理员偏好约束

        Args:
            schedule: 排班方案
            caregivers: 护理员列表
            elders: 老人列表
            shift_config: 班次配置

        Returns:
            违反约束的次数
        """
        if "shift_assignments" not in schedule:
            return 0

        violations = 0

        # 假设每个护理员有偏好的班次类型
        caregiver_preferences = {}
        for caregiver in caregivers:
            # 随机生成偏好，实际应该从数据库获取
            preferred_shifts = random.sample(
                list(shift_config.keys()), min(2, len(shift_config))
            )
            caregiver_preferences[caregiver.id] = preferred_shifts

        # 检查每个班次分配是否满足护理员偏好
        for assignment in schedule["shift_assignments"]:
            caregiver_id = assignment.caregiver_id
            shift_type = assignment.shift_type

            if (
                caregiver_id in caregiver_preferences
                and shift_type not in caregiver_preferences[caregiver_id]
            ):
                violations += 1

        return violations

    def check_skill_match(self, schedule, caregivers, elders, shift_config):
        """检查技能匹配约束

        Args:
            schedule: 排班方案
            caregivers: 护理员列表
            elders: 老人列表
            shift_config: 班次配置

        Returns:
            违反约束的次数
        """
        if "time_slot_assignments" not in schedule:
            return 0

        violations = 0

        # 假设每个护理员有技能列表
        caregiver_skills = {}
        for caregiver in caregivers:
            # 实际应该从数据库获取
            caregiver_skills[caregiver.id] = (
                set(caregiver.skills.split(","))
                if hasattr(caregiver, "skills") and caregiver.skills
                else set()
            )

        # 假设每个活动需要特定的技能
        activity_required_skills = {}
        # 实际应该从数据库获取

        # 检查每个时间段分配是否满足技能要求
        for assignment in schedule["time_slot_assignments"]:
            caregiver_id = assignment.caregiver_id
            activity_id = assignment.activity_id

            if (
                activity_id in activity_required_skills
                and caregiver_id in caregiver_skills
            ):
                required_skills = activity_required_skills[activity_id]
                caregiver_skill_set = caregiver_skills[caregiver_id]

                if not required_skills.issubset(caregiver_skill_set):
                    violations += 1

        return violations

    def check_workload_balance(self, schedule, caregivers, elders, shift_config):
        """检查工作负载平衡约束

        Args:
            schedule: 排班方案
            caregivers: 护理员列表
            elders: 老人列表
            shift_config: 班次配置

        Returns:
            违反约束的次数
        """
        if "shift_assignments" not in schedule:
            return 0

        violations = 0

        # 统计每个护理员的班次数
        caregiver_shifts = {}
        for assignment in schedule["shift_assignments"]:
            caregiver_id = assignment.caregiver_id
            if caregiver_id not in caregiver_shifts:
                caregiver_shifts[caregiver_id] = 0

            caregiver_shifts[caregiver_id] += 1

        # 计算平均班次数
        if not caregiver_shifts:
            return 0

        shift_counts = list(caregiver_shifts.values())
        mean_shifts = sum(shift_counts) / len(shift_counts)

        # 计算标准差
        std_dev = (
            sum((count - mean_shifts) ** 2 for count in shift_counts)
            / len(shift_counts)
        ) ** 0.5

        # 如果标准差大于阈值，则认为工作负载不平衡
        threshold = 1.0
        if std_dev > threshold:
            violations = int(std_dev - threshold)

        return violations

    def check_shift_continuity(self, schedule, caregivers, elders, shift_config):
        """检查班次连续性约束

        Args:
            schedule: 排班方案
            caregivers: 护理员列表
            elders: 老人列表
            shift_config: 班次配置

        Returns:
            违反约束的次数
        """
        if "shift_assignments" not in schedule:
            return 0

        violations = 0

        # 按护理员和日期分组
        shifts_by_caregiver = {}
        for assignment in schedule["shift_assignments"]:
            caregiver_id = assignment.caregiver_id
            if caregiver_id not in shifts_by_caregiver:
                shifts_by_caregiver[caregiver_id] = {}

            date_key = str(assignment.shift_date)
            if date_key not in shifts_by_caregiver[caregiver_id]:
                shifts_by_caregiver[caregiver_id][date_key] = []

            shifts_by_caregiver[caregiver_id][date_key].append(assignment.shift_type)

        # 检查每个护理员的班次连续性
        for caregiver_id, dates in shifts_by_caregiver.items():
            # 按日期排序
            sorted_dates = sorted(dates.keys())

            # 检查相邻日期的班次是否连续
            for i in range(1, len(sorted_dates)):
                prev_date = sorted_dates[i - 1]
                curr_date = sorted_dates[i]

                prev_shifts = dates[prev_date]
                curr_shifts = dates[curr_date]

                # 如果前一天和当前天的班次类型不同，则认为不连续
                if set(prev_shifts) != set(curr_shifts):
                    violations += 1

        return violations
